package top.ply.messageservice.service.Impl;

import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import sun.misc.BASE64Encoder;
import top.ply.common_unit.network.NetWork;
import top.ply.message.pojo.MsgResult;
import top.ply.message.service.TelephoneMsgService;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;

/**
 * @Author: Amosen
 *
 * 这里之后要利用AOP来做日志，保存相关数据
 */

@Service
@DubboService(interfaceClass = TelephoneMsgService.class)
@RefreshScope
public class TelephoneMsgServiceImpl implements TelephoneMsgService {

    private static final ExecutorService saveResultThreadPool = Executors.newCachedThreadPool();

    @Value("${sms.alibaba.accessKeyID}")
    private String smsAccessKeyID;

    @Value("${sms.alibaba.accessSecret}")
    private String smsAccessSecret;

    Logger logger = LoggerFactory.getLogger(TelephoneMsgService.class);

    private static final String TELEPHONE_REG_EXP = "^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$";

    private static Pattern TELEPHONE_PATTERN = Pattern.compile(TELEPHONE_REG_EXP);

    private static final String SEND_SMS_URL = "http(s)://dysmsapi.aliyuncs.com/";

    @Value("${sms.verifycode.signName}")
    private String verifyCodeSmsSignName;

    @Value("${sms.verifycode.templateCode")
    private String verifyCodeSmsTemplateCode;

    @Override
    public boolean sendVerifyCode(String phoneNumbers, String code) {
        if (!checkVerifyCodeParams(phoneNumbers, code)) {
            return false;
        } else {
            Map<String, String> data = wrapVerifyCodeData(phoneNumbers, code);
            Map<String, String> resp = sendVerifyCodeData(data);
            saveResultThreadPool.submit(() -> {
                saveResult(resp);
            });
            return isSendSuccess(resp);
        }
    }

    @Override
    public MsgResult getMsgResult(String msgID) {
        return null;
    }

    boolean checkVerifyCodeParams(String phoneNumbers, String code) {
        if (!TELEPHONE_PATTERN.matcher(phoneNumbers).matches()) {
            return false;
        }
        return StringUtils.hasText(phoneNumbers) && StringUtils.hasText(code);
    }

    Map<String, String> wrapVerifyCodeData(String phoneNumbers, String code) {
        Map<String, String> data = wrapCommonData();
        data.put("Action", "SendSms");
        data.put("PhoneNumbers", phoneNumbers);
        data.put("SignName", verifyCodeSmsSignName);
        data.put("TemplateCode", verifyCodeSmsTemplateCode);
        String templateParam = "{\"code:\":\"" + code + "\"}";
        data.put("TemplateParam", templateParam);
        return data;
    }

    final Map<String, String> wrapCommonData() {
        Map<String, String> commonData = new HashMap<>();
        commonData.put("SignatureMethod", "HMAC-SHA1");
        commonData.put("SignatureNonce", UUID.randomUUID().toString());
        commonData.put("AccessKeyId", smsAccessKeyID);
        commonData.put("SignatureVersion", "1.0");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        sdf.setTimeZone(new SimpleTimeZone(0, "GMT"));
        commonData.put("Timestamp", sdf.format(new Date()));
        commonData.put("Version", "2017-05-25");
        commonData.put("RegionId", "cn-hangzhou");
        return signCommonData(commonData);
    }

    final Map<String, String> signCommonData(Map<String, String> commonData) {
        if (commonData.containsKey("Signature")) {
            commonData.remove("Signature");
        }
        TreeMap<String, String> sortedMap = new TreeMap<>();
        sortedMap.putAll(commonData);
        Iterator<String> it = sortedMap.keySet().iterator();
        StringBuilder sb = new StringBuilder();
        while (it.hasNext()) {
            String key = it.next();
            sb.append("&")
                    .append(specialUrlEncode(key))
                    .append("=")
                    .append(specialUrlEncode(sortedMap.get(key)));
        }
        String sortedQueryString = sb.substring(1);
        StringBuilder stringToSign = new StringBuilder();
        stringToSign.append("GET")
                .append("&")
                .append(specialUrlEncode("/"))
                .append("&")
                .append(specialUrlEncode(sortedQueryString));
        String sign = signString(smsAccessSecret + "&", stringToSign.toString());
        String signature = specialUrlEncode(sign);
        commonData.put("Signature", signature);
        return commonData;
    }

    static final String specialUrlEncode(String value) {
        try {
            return URLEncoder.encode(value, "UTF-8")
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            return "";
        }
    }

    static final String signString(String accessSecret, String stringToSign) {
        try {
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(new SecretKeySpec(accessSecret.getBytes("UTF-8"), "HmacSHA1"));
            byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
            return new BASE64Encoder().encode(signData);
        } catch (Exception e) {
            return "";
        }
    }

    Map<String, String> sendVerifyCodeData(Map<String, String> data) {
        return NetWork.doGet(SEND_SMS_URL, data, null);
    }

    void saveResult(Map<String, String> resp) {
        // TODO: 将发送结果保存到数据库
    }

    boolean isSendSuccess(Map<String, String> resp) {
        if (resp.containsKey("Code")) {
            return "OK".equals(resp.get("Code"));
        }
        return false;
    }
}
